package com.atguigu.mock;

import cn.hutool.core.lang.WeightRandom;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.common.base.entity.AutoIdBase;
import com.atguigu.common.util.DateUtil;
import com.atguigu.mock.entity.*;
import com.atguigu.mock.repository.*;
import com.github.javafaker.Faker;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;


@Component
public class MockMain {

    private Boolean isInit;
    private LocalDateTime currentTime;

    @Value("${mock.enthusiasm}")
    private double enthusiasm;

    private final DateUtil dateUtil;
    private final Faker faker;
    private final BusinessPartnerRepository businessPartnerRepository;
    private final ContractRepository contractRepository;
    private final CreditFacilityRepository creditFacilityRepository;
    private final CreditFacilityStatusRepository creditFacilityStatusRepository;
    private final DepartmentRepository departmentRepository;
    private final EmployeeRepository employeeRepository;
    private final CreditRepository creditRepository;
    private final IndustryRepository industryRepository;
    private final ReplyRepository replyRepository;

    private List<Employee> employees;
    private List<Industry> industries;
    private List<Department> departments;
    private List<BusinessPartner> businessPartners;
    private Set<CreditFacility> creditFacilities;
    private Set<CreditFacilityStatus> creditFacilityStatuses;
    private Set<Credit> credits;
    private Set<Contract> contracts;
    private Set<Reply> replies;
    private final WeightRandom<Integer> downstream = new WeightRandom<>();

    @PostConstruct
    public void init() {
        Optional<CreditFacility> lastElement = creditFacilityRepository.findFirstByOrderByUpdateTimeDesc();
        isInit = !lastElement.isPresent();
        currentTime = isInit ? dateUtil.firstDay(8) : dateUtil.newDay(lastElement.get().getUpdateTime(), 8);
    }

    public MockMain(DateUtil dateUtil, Faker faker, BusinessPartnerRepository businessPartnerRepository, ContractRepository contractRepository, CreditFacilityRepository creditFacilityRepository, CreditFacilityStatusRepository creditFacilityStatusRepository, DepartmentRepository departmentRepository, EmployeeRepository employeeRepository, CreditRepository creditRepository, IndustryRepository industryRepository, ReplyRepository replyRepository) {
        this.dateUtil = dateUtil;
        this.faker = faker;
        this.businessPartnerRepository = businessPartnerRepository;
        this.contractRepository = contractRepository;
        this.creditFacilityRepository = creditFacilityRepository;
        this.creditFacilityStatusRepository = creditFacilityStatusRepository;
        this.departmentRepository = departmentRepository;
        this.employeeRepository = employeeRepository;
        this.creditRepository = creditRepository;
        this.industryRepository = industryRepository;
        this.replyRepository = replyRepository;
    }

    public void employeeMock() {
        List<Department> departments = departmentRepository.findAll().stream().filter(department -> department.getDepartmentLevel() == 3).collect(Collectors.toList());
        LocalDateTime timestamp = dateUtil.firstDay(0);
        employees = new ArrayList<>();
        for (Department department : departments) {
            // 业务经办，信审经办
            for (int i = 0; i < RandomUtil.randomInt(40, 70); i++) {
                employees.add(new Employee(timestamp, faker.name().fullName(), 1L, department));
                employees.add(new Employee(timestamp, faker.name().fullName(), 4L, department));
            }
            // 一级审核员
            for (int i = 0; i < RandomUtil.randomInt(20, 40); i++) {
                employees.add(new Employee(timestamp, faker.name().fullName(), 2L, department));
                employees.add(new Employee(timestamp, faker.name().fullName(), 5L, department));
                employees.add(new Employee(timestamp, faker.name().fullName(), 6L, department));
            }
            // 二级审核员
            for (int i = 0; i < RandomUtil.randomInt(10, 30); i++) {
                employees.add(new Employee(timestamp, faker.name().fullName(), 7L, department));
            }
            for (int i = 0; i < RandomUtil.randomInt(2, 5); i++) {
                // 经理
                employees.add(new Employee(timestamp, faker.name().fullName(), 3L, department));
                employees.add(new Employee(timestamp, faker.name().fullName(), 8L, department));
            }
        }
        employeeRepository.saveAll(employees);
    }

    public void industryMock() {
        industries = new ArrayList<>();
        LocalDateTime timestamp = dateUtil.firstDay(0);
        industries.add(new Industry(timestamp, "工程建设", 1, null));
        industries.add(new Industry(timestamp, "轻工", 1, null));
        industries.add(new Industry(timestamp, "土木", 2, industries.get(0)));
        industries.add(new Industry(timestamp, "交通", 2, industries.get(0)));
        industries.add(new Industry(timestamp, "纺织业", 2, industries.get(1)));
        industries.add(new Industry(timestamp, "服装业", 2, industries.get(1)));
        industries.add(new Industry(timestamp, "房建", 3, industries.get(2)));
        industries.add(new Industry(timestamp, "城市地下空间结构与工程", 3, industries.get(2)));
        industries.add(new Industry(timestamp, "道桥", 3, industries.get(3)));
        industries.add(new Industry(timestamp, "印染行业", 3, industries.get(4)));
        industries.add(new Industry(timestamp, "纱线行业", 3, industries.get(4)));
        industries.add(new Industry(timestamp, "纺织成衣行业", 3, industries.get(4)));
        industries.add(new Industry(timestamp, "服装生产加工行业", 3, industries.get(5)));
        industries.add(new Industry(timestamp, "服装品牌行业", 3, industries.get(5)));
        industries.add(new Industry(timestamp, "服装销售行业", 3, industries.get(5)));
        industries.add(new Industry(timestamp, "服装配件行业", 3, industries.get(5)));
        industryRepository.saveAll(industries);
    }

    public void departmentMock() {
        departments = new ArrayList<>();
        LocalDateTime timestamp = dateUtil.firstDay(0);
        departments.add(new Department(timestamp, "工程机械部", 1, null));
        departments.add(new Department(timestamp, "轻工业部", 1, null));
        departments.add(new Department(timestamp, "工程机械南区", 2, departments.get(0)));
        departments.add(new Department(timestamp, "工程机械北区", 2, departments.get(0)));
        departments.add(new Department(timestamp, "轻工业部南区", 2, departments.get(1)));
        departments.add(new Department(timestamp, "轻工业部北区", 2, departments.get(1)));
        departments.add(new Department(timestamp, "南区交通部", 3, departments.get(2)));
        departments.add(new Department(timestamp, "南区机械部", 3, departments.get(2)));
        departments.add(new Department(timestamp, "北区交通部", 3, departments.get(3)));
        departments.add(new Department(timestamp, "北区机械部", 3, departments.get(3)));
        departments.add(new Department(timestamp, "南区纺织部", 3, departments.get(4)));
        departments.add(new Department(timestamp, "南区服装部", 3, departments.get(4)));
        departments.add(new Department(timestamp, "北区纺织部", 3, departments.get(5)));
        departments.add(new Department(timestamp, "北区服装部", 3, departments.get(5)));
        departmentRepository.saveAll(departments);
    }

    public void businessPartnerMock() {
        LocalDateTime timestamp = dateUtil.firstDay(0);
        businessPartners = ArrayUtil.map(new BusinessPartner[10], businessPartner -> new BusinessPartner(
                RandomUtil.randomString(8),
                timestamp
        ));
        businessPartnerRepository.saveAll(businessPartners);
    }

    public <T extends AutoIdBase> List<T> sortByUpdateTime(Collection<T> entities) {
        return entities.stream().sorted(Comparator.comparing(AutoIdBase::getUpdateTime)).collect(Collectors.toList());
    }

    /**
     * 每次调用会有80%几率由一位随机业务员随机生成一份申请
     */
    public void createCreditFacility() {
        if (RandomUtil.randomDouble() < 0.8) {
            List<Employee> salesmen = this.employees.stream().filter(employee -> employee.getType() == 1L).collect(Collectors.toList());
            List<Industry> industries = this.industries.stream().filter(industry -> industry.getIndustryLevel() == 3).collect(Collectors.toList());
            CreditFacility creditFacility = new CreditFacility(
                    currentTime,
                    RandomUtil.randomEle(businessPartners),
                    RandomUtil.randomEle(salesmen),
                    RandomUtil.randomEle(industries),
                    RandomUtil.randomEle(new String[]{"现代农业", "公共服务", "医疗化工", "建设", "创新三", "工业装备", "民生消费"}),
                    BigDecimal.valueOf(RandomUtil.randomDouble(10000, 1000000))
            );
            List<CreditFacilityStatus> statuses = new ArrayList<>();
            CreditFacilityStatus status = new CreditFacilityStatus(currentTime, 1, creditFacility, creditFacility.getSalesman(), 1);
            statuses.add(status);
            creditFacility.setStatusList(statuses);
            creditFacility.setStatus(1);
            creditFacilities.add(creditFacility);
            creditFacilityStatuses.add(status);
        }

    }

    /**
     * @param employeeType  相关员工类型
     * @param avgMinutes    平均操作时间
     * @param currentStatus 当前状态
     * @param downstream    下游状态
     */
    private void creditFacilityHandler(long employeeType,
                                       long avgMinutes,
                                       int currentStatus,
                                       WeightRandom<Integer> downstream) {
        creditFacilityHandler(employeeType, avgMinutes, currentStatus, downstream, creditFacility -> {
        });
    }

    /**
     * @param employeeType  相关员工类型
     * @param avgMinutes    平均操作时间
     * @param currentStatus 当前状态
     * @param downstream    下游状态
     * @param customAction  自定义动作
     */
    private void creditFacilityHandler(long employeeType,
                                       long avgMinutes,
                                       int currentStatus,
                                       WeightRandom<Integer> downstream,
                                       Consumer<? super CreditFacility> customAction) {
        this.creditFacilities.stream()
                .filter(creditFacility -> creditFacility.getStatus() == currentStatus && dateUtil.longEnough(creditFacility.getUpdateTime(), currentTime, avgMinutes))
                .filter(creditFacility -> RandomUtil.randomDouble() < enthusiasm)
                .peek(creditFacility -> {
                    List<CreditFacilityStatus> statusList = creditFacility.getStatusList();
                    LocalDateTime time = dateUtil.later(currentTime, RandomUtil.randomLong(0, 60000));
                    creditFacility.setUpdateTime(time);
                    Employee employee = null;
                    if (employeeType > 0) {
                        List<Employee> employees = this.employees.stream()
                                .filter(emp -> emp.getType() == employeeType && emp.getDepartment().equals(creditFacility.getSalesman().getDepartment()))
                                .collect(Collectors.toList());
                        employee = RandomUtil.randomEle(employees);
                    } else if (employeeType == 0) {
                        CreditFacilityStatus lastStatus = statusList.get(statusList.size() - 1);
                        employee = lastStatus.getEmployee();
                    }
                    Integer status = downstream.next();
                    CreditFacilityStatus creditFacilityStatus = new CreditFacilityStatus(time, 1, creditFacility, employee, status);
                    if (Arrays.asList(2, 6, 9, 11, 13, 15, 17, 18, 20, 21).contains(status)) {
                        creditFacilityStatus.setActionTaken(2);
                    }
                    statusList.add(creditFacilityStatus);
                    creditFacilityStatuses.add(creditFacilityStatus);
                    creditFacility.setStatus(status);

                })
                .forEach(customAction);
    }

    public void handleCreditFacility() {
        // 风控
        creditFacilityHandler(2, 180, 1,
                downstream.clear()
                        .add(2, 5)
                        .add(3, 90)
                        .add(21, 1));
        // 风控再审核
        creditFacilityHandler(3, 180, 2,
                downstream.clear()
                        .add(3, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 分配信审经办
        creditFacilityHandler(-1, 30, 3,
                downstream.clear()
                        .add(4, 100)
                        .add(21, 1));
        // 信审经办审核
        creditFacilityHandler(4, 60, 4,
                downstream.clear()
                        .add(5, 90)
                        .add(6, 5)
                        .add(20, 5)
                        .add(21, 1));
        // 提交业务反馈
        creditFacilityHandler(0, 60, 5,
                downstream.clear()
                        .add(7, 90)
                        .add(21, 1));
        // 信审经办复议
        creditFacilityHandler(4, 60, 6,
                downstream.clear()
                        .add(5, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 一级评审
        creditFacilityHandler(5, 180, 7,
                downstream.clear()
                        .add(8, 90)
                        .add(9, 5)
                        .add(20, 5)
                        .add(21, 1),
                creditFacility -> {
                    if (RandomUtil.randomDouble() < 0.5) {
                        List<CreditFacilityStatus> statusList = creditFacility.getStatusList();
                        CreditFacilityStatus lastStatus = statusList.get(statusList.size() - 1);
                        if (lastStatus.getStatus() != 21) {
                            List<Employee> employees = this.employees.stream()
                                    .filter(emp -> emp.getType() == 5 && emp.getDepartment().equals(creditFacility.getSalesman().getDepartment()))
                                    .collect(Collectors.toList());
                            Employee signatory = RandomUtil.randomEle(employees);
                            while (signatory.equals(lastStatus.getEmployee())) {
                                signatory = RandomUtil.randomEle(employees);
                            }
                            lastStatus.setSignatory(signatory);
                        }
                    }
                });
        // 二级评审
        creditFacilityHandler(7, 180, 8,
                downstream.clear()
                        .add(10, 90)
                        .add(11, 5)
                        .add(20, 5)
                        .add(21, 1));
        // 一级评审复议
        creditFacilityHandler(5, 60, 9,
                downstream.clear()
                        .add(7, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 项目评审会
        creditFacilityHandler(-1, 180, 10,
                downstream.clear()
                        .add(12, 90)
                        .add(13, 5)
                        .add(20, 5)
                        .add(21, 1));
        // 二级评审复议
        creditFacilityHandler(7, 60, 11,
                downstream.clear()
                        .add(8, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 总经理审核
        creditFacilityHandler(8, 120, 12,
                downstream.clear()
                        .add(14, 90)
                        .add(15, 5)
                        .add(20, 5)
                        .add(21, 1));
        // 项目评审会复议
        creditFacilityHandler(-1, 60, 13,
                downstream.clear()
                        .add(10, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 批复审核
        creditFacilityHandler(-1, 60, 14,
                downstream.clear()
                        .add(16, 90)
                        .add(17, 5)
                        .add(20, 5)
                        .add(21, 1));
        // 总经理复议
        creditFacilityHandler(8, 60, 15,
                downstream.clear()
                        .add(12, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 出具批复
        creditFacilityHandler(8, 180, 16,
                downstream.clear()
                        .add(19, 90)
                        .add(18, 10)
                        .add(21, 1),
                creditFacility -> {
                    // 新增授信
                    if (creditFacility.getStatus() == 19) {
                        creditFacility.setUpdateTime(currentTime);
                        BigDecimal creditAmount = creditFacility.getCreditAmount();
                        Reply reply = new Reply(
                                currentTime,
                                creditFacility,
                                RandomUtil.randomDouble() < 0.8 ? creditAmount : BigDecimal.valueOf(creditAmount.doubleValue() * RandomUtil.randomDouble(0.8, 1.2)),
                                RandomUtil.randomInt(12, 36),
                                BigDecimal.valueOf(RandomUtil.randomDouble(0.01, 0.2))
                        );
                        creditFacility.setReply(reply);
                        Credit credit = new Credit(currentTime, creditFacility, reply.getCreditAmount());
                        credit.setStatus(1);
                        creditFacility.setCredit(credit);
                        replies.add(reply);
                        credits.add(credit);
                    }
                });
        // 批复审核复议
        creditFacilityHandler(-1, 60, 17,
                downstream.clear()
                        .add(14, 50)
                        .add(20, 50)
                        .add(21, 1));
        // 批复不满复议
        creditFacilityHandler(8, 180, 18,
                downstream.clear()
                        .add(7, 90)
                        .add(16, 10)
                        .add(21, 1));
    }

    public void creditOccupy() {

        int avgMinutes = 180;
        credits.stream().filter(credit -> credit.getStatus() == 1                                              // 没有取消
                        && dateUtil.longEnough(credit.getUpdateTime(), currentTime, avgMinutes))
                .filter(credit -> RandomUtil.randomDouble() < enthusiasm)
                .forEach(credit -> {
                    credit.setUpdateTime(currentTime);
                    if (RandomUtil.randomDouble() < 0.9) {
                        credit.setStatus(2);
                        credit.setCreditOccupyTime(currentTime);
                    } else {
                        credit.setStatus(4);
                        credit.setCancelTime(currentTime);
                    }
                });

    }

    public void contractProduce() {
        int avgMinutes = 60;
        credits.stream().filter(credit -> credit.getStatus() == 2
                        && credit.getContract() == null
                        && dateUtil.longEnough(credit.getUpdateTime(), currentTime, avgMinutes))
                .filter(credit -> RandomUtil.randomDouble() < enthusiasm)
                .map(credit -> {
                    credit.setUpdateTime(currentTime);
                    Contract contract = null;
                    if (RandomUtil.randomDouble() < 0.9) {
                        contract = new Contract(currentTime, credit);
                        contract.setStatus(1);
                        credit.setContract(contract);
                        credit.setStatus(3);
                        credit.setContractProduceTime(currentTime);
                    } else {
                        credit.setStatus(4);
                        credit.setCancelTime(currentTime);
                    }
                    return contract;
                })
                .filter(Objects::nonNull)
                .forEach(contracts::add);
    }

    public void contractSign() {

        int avgMinutes = 120;
        contracts.stream().filter(contract -> contract.getStatus() == 1
                        && dateUtil.longEnough(contract.getUpdateTime(), currentTime, avgMinutes * 10000))
                .filter(contract -> RandomUtil.randomDouble() < enthusiasm)
                .forEach(contract -> {
                    contract.setUpdateTime(currentTime);
                    contract.setSignedTime(currentTime);
                    contract.setStatus(2);
                });
    }

    public void contractExecute() {

        int avgMinutes = 960;
        contracts.stream().filter(contract -> contract.getStatus() == 2
                        && dateUtil.longEnough(contract.getUpdateTime(), currentTime, avgMinutes * 10000))
                .filter(contract -> RandomUtil.randomDouble() < enthusiasm)
                .forEach(contract -> {
                    contract.setUpdateTime(currentTime);
                    contract.setExecutionTime(currentTime);
                    contract.setStatus(3);
                });
    }

    public void persistAll() {
        this.businessPartnerRepository.saveAll(sortByUpdateTime(businessPartners));
        this.employeeRepository.saveAll(sortByUpdateTime(employees));
        this.industryRepository.saveAll(sortByUpdateTime(industries));
        this.departmentRepository.saveAll(sortByUpdateTime(departments));
        this.creditFacilityStatusRepository.saveAll(sortByUpdateTime(creditFacilityStatuses));
        this.creditFacilityRepository.saveAll(sortByUpdateTime(creditFacilities));
        this.creditRepository.saveAll(sortByUpdateTime(credits));
        this.replyRepository.saveAll(sortByUpdateTime(replies));
        this.contractRepository.saveAll(sortByUpdateTime(contracts));
    }

    @Transactional
    public void run() {
        System.out.println("正在初始化缓存");
        if (isInit) {
            industryMock();
            departmentMock();
            employeeMock();
            businessPartnerMock();
            creditFacilities = new HashSet<>();
            creditFacilityStatuses = new HashSet<>();
            contracts = new HashSet<>();
            replies = new HashSet<>();
            credits = new HashSet<>();
        } else {
            industries = industryRepository.findAll();
            departments = departmentRepository.findAll();
            employees = employeeRepository.findAll();
            businessPartners = businessPartnerRepository.findAll();
            creditFacilities = new HashSet<>(creditFacilityRepository.findAll());
            credits = new HashSet<>(creditRepository.findAll());
            contracts = new HashSet<>(contractRepository.findAll());
            replies = new HashSet<>(replyRepository.findAll());
            creditFacilityStatuses = new HashSet<>(creditFacilityStatusRepository.findAll());
        }

        for (int i = 0; i < 540; i++) {
            currentTime = dateUtil.later(currentTime, 1000 * 60);
            try {
                System.out.write(("\r" + "正在模拟" + currentTime).getBytes());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            createCreditFacility();
            handleCreditFacility();
            creditOccupy();
            contractProduce();
            contractSign();
            contractExecute();
            persistAll();
        }
        System.out.println();
    }
}
